//==================================================================
//  Copyright (C) 2006-2007  Davide Pasca
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//==================================================================
/// @file image_base.h
///
///
/// @author Davide Pasca
///
//==================================================================

#ifndef PUTILS_IMAGE_BASE_H
#define PUTILS_IMAGE_BASE_H

#include "../../psys/include/psys.h"
#include "memfile.h"

//==================================================================
namespace PUtils
{

//==================================================================
///
//==================================================================
class ImageBase : public PSYS::sync_obj
{
public:
	static const int	MAX_CHANNELS = 8;

	//------------------------------------------------------------------
	class Channel
	{
	public:
		enum Type
		{
			TYPE_EMPTY,
			TYPE_RED,
			TYPE_GREEN,
			TYPE_BLUE,
			TYPE_ALPHA,
			TYPE_LUMINANCE,
			TYPE_INDEX,
		};

		enum Format
		{
			FORMAT_U8,
			FORMAT_U16,
			FORMAT_U32,
			FORMAT_F32,
			FORMAT_N
		};

		typedef void (*ConvertCallback)( void *desp, const void *srcp );
		typedef float (*GetAsF32Func)( const void *srcp );
		typedef void (*SetAsF32Func)( void *desp, float val );

		Type		_type;
	private:
		Format		_format;
	public:
		u_char		*_datap;
		bool		_owns_data;
		int			_pitch;

	public:
		Channel() :
			_type(TYPE_EMPTY), _format(FORMAT_U8), _datap(NULL), _owns_data(false), _pitch(0),
			GetAsF32(0),
			SetAsF32(0)
		{
		}
		~Channel();

		void	freeChannel();

		inline Format	GetFormat() const
		{
			return _format;
		}

		void SetFormat( Format format );

	private:
		static void Assign_U8_U8( void *desp, const void *srcp )	{	*((u_char *)desp)	= *((u_char *)srcp);	}
		static void Assign_U16_U16( void *desp, const void *srcp )	{	*((u_short *)desp)	= *((u_short *)srcp);	}
		static void Assign_U32_U32( void *desp, const void *srcp )	{	*((u_int *)desp)	= *((u_int *)srcp);		}
		static void Assign_F32_F32( void *desp, const void *srcp )	{	*((float *)desp)	= *((float *)srcp);		}

		static void Assign_U8_U16( void *desp, const void *srcp ){	*((u_char *)desp) = *((u_short *)srcp) >> 8;	}
		static void Assign_U16_U8( void *desp, const void *srcp ){	*((u_short *)desp) = (u_int)*((u_char *)srcp) << 8;	}

		static void Assign_U8_U32( void *desp, const void *srcp ){	*((u_char *)desp) = *((u_int *)srcp) >> 24;	}
		static void Assign_U32_U8( void *desp, const void *srcp ){	*((u_int *)desp) = (u_int)*((u_char *)srcp) << 24;	}

		static void Assign_U8_F32( void *desp, const void *srcp ){	*((float *)desp) = (int)(*((u_char *)srcp) * 255.0f);	}
		static void Assign_F32_U8( void *desp, const void *srcp ){	*((u_char *)desp) = *((float *)srcp) * (1.0f/255);	}

		static void Assign_U16_U32( void *desp, const void *srcp ){	*((u_short *)desp) = *((u_int *)srcp) >> 16;	}
		static void Assign_U32_U16( void *desp, const void *srcp ){	*((u_int *)desp) = *((u_short *)srcp) << 16;	}

		static void Assign_U16_F32( void *desp, const void *srcp ){	*((float *)desp) = (int)(*((u_short *)srcp) * 65535.0f);	}
		static void Assign_F32_U16( void *desp, const void *srcp ){	*((u_short *)desp) = *((float *)srcp) * (1.0f/65535);	}

		static void Assign_U32_F32( void *desp, const void *srcp ){	*((float *)desp) = (int)(*((u_int *)srcp) * 4294967295.0f);	}
		static void Assign_F32_U32( void *desp, const void *srcp ){	*((u_int *)desp) = *((float *)srcp) * (1.0f/4294967295);	}

		static ConvertCallback	_conv_table[FORMAT_N][FORMAT_N];

		static float GetAsF32_U8( const void *srcp ){	return *((u_char *)srcp) * (1.0f/255);			}
		static float GetAsF32_U16( const void *srcp ){	return *((u_short *)srcp) * (1.0f/65535);			}
		static float GetAsF32_U32( const void *srcp ){	return *((u_int *)srcp) * (1.0f/4294967295);	}
		static float GetAsF32_F32( const void *srcp ){	return *((float *)srcp);						}

		static void SetAsF32_U8( void *desp, float val ) {	*((u_char *)desp) = val * 255;		}
		static void SetAsF32_U16( void *desp, float val ){	*((u_short *)desp) = val * 65535;	}
		static void SetAsF32_U32( void *desp, float val ){	*((u_int *)desp) = val * 4294967295.0f;	}
		static void SetAsF32_F32( void *desp, float val ){	*((float *)desp) = val;						}

	public:
		static ConvertCallback GetConverter( Format des_fmt, Format src_fmt )
		{
			PASSERT( des_fmt >= 0 && des_fmt < FORMAT_N );
			PASSERT( src_fmt >= 0 && src_fmt < FORMAT_N );

			return _conv_table[des_fmt][src_fmt];
		}

		float (*GetAsF32)( const void *srcp );
		void (*SetAsF32)( void *desp, float val );

		/*
		static GetF32Func GetGetF32Func( Format des_fmt )
		{
			PASSERT( des_fmt >= 0 && des_fmt < FORMAT_N );
			PASSERT( src_fmt >= 0 && src_fmt < FORMAT_N );

			return _conv_table[des_fmt][src_fmt];
		}
		typedef float (*GetF32Func)( const void *srcp );
		typedef void (*SetF32Func)( const void *srcp, float val );
*/
	};

	//------------------------------------------------------------------
	enum BaseFormat
	{
		BF_NONE,
		BF_INDEX8,
		BF_A8,
		BF_L8,
		BF_A8L8,
		BF_L8A8,
		BF_R8G8B8,
		BF_B8G8R8,
		BF_R8G8B8A8,
		BF_B8G8R8A8,
		BF_B8G8R8X8,
		BF_R32FG32FB32F,
	};

	//------------------------------------------------------------------
	struct LoadParams
	{
		enum FileType
		{
			FT_UNKNWON,
			FT_JPEG,
			FT_BMP
		};

		FileType	_file_type;

		bool		_do_convert_alpha_luminance;
		bool		_do_convert_truecolor;
		bool		_do_force_alpha;

		bool		_out_transparency_detected;

		LoadParams() :
			_file_type(FT_UNKNWON),
			_do_convert_alpha_luminance(false),
			_do_convert_truecolor(true),
			_do_force_alpha(false),
			_out_transparency_detected(false)
		{
		}

		void SetTypeFromFilename( const TCHAR *fnamep );
	};

private:
	int			_n_channels;
	Channel		_channels[MAX_CHANNELS];
	BaseFormat	_base_format;
	u_int		_w;
	u_int		_h;
	u_int		_pitch;
	u_int		_pix_size;

public:
	ImageBase();
	ImageBase( BaseFormat base_format );
	ImageBase( BaseFormat base_format, u_int w, u_int h, u_char *data_srcp=NULL, int pitch=0 );
	~ImageBase();

	static bool		IsLoadableFilename_s( const TCHAR *fnamep );

	void			LoadAutodetect( const TCHAR *fnamep, LoadParams *paramsp );
	void			Load( Memfile &mf, LoadParams *paramsp );
	void			LoadBMP( Memfile &mf, LoadParams *paramsp );
	void			LoadJPEG( Memfile &mf, LoadParams *paramsp );
	void			SaveJPEG( const TCHAR *filename, int quality );
	void			SaveJPEG( Memfile &mf, int quality );

	void			Setup( BaseFormat base_format, int w, int h, u_char *data_srcp, int pitch ) throw(...);

	u_int			GetWidth() const					{	return _w;	};
	u_int			GetHeight() const					{	return _h;	};
	BaseFormat		GetBaseFormat() const				{	return _base_format;	};
	int				FindChannelByType( Channel::Type type ) const;
	const u_char	*GetData( int chan_idx=0 ) const	{	return _channels[chan_idx]._datap; }
	u_char			*GetData( int chan_idx=0 )			{	return _channels[chan_idx]._datap; }

	u_char			*GetPixelPtr( int chan_idx, int x, int y );
	const u_char	*GetPixelPtr( int chan_idx, int x, int y ) const;

	u_char			GetChanU8( int chan_idx, int x, int y ) const;
	void			SetChanU8( int chan_idx, int x, int y, u_char val );

	float			GetChanF32( int chan_idx, int x, int y ) const;
	void			SetChanF32( int chan_idx, int x, int y, float val );

	u_char			GetChanU8FastClamp( int chan_idx, int x, int y ) const
	{
		if ( x < 0 )	x = 0;	else
		if ( x >= _w )	x = _w-1;

		if ( y < 0 )	y = 0; else
		if ( y >= _h )	y = _h-1;

		const Channel	* const chp = &_channels[ chan_idx ];

		return chp->_datap[ x * _pix_size + y * chp->_pitch ];
	}

	u_int			GetImagePitch() const					{	return _pitch; }
	int				GetChanPitch( int chan_idx ) const	{	return _channels[chan_idx]._pitch; }

	void			BlitFrom( const ImageBase &src, int sx, int sy, int w, int h,
								int dx, int dy,
								bool *out_transp_detectedp=NULL,
								ImageBase *src_palp=NULL );

	void			CopyChannelFormats( const ImageBase &src );

	void			ScaleQuarter();

private:
	void			setBaseFormat( BaseFormat base_format );
	void			freeChannels();
};
//==================================================================
};

#endif
